/*
* Copyright (c) xiake6. All Rights Reserved.
* @Owner:fenglibin
* @Date:  2017-10-16 15:25
*/
package net.xiake6.springcloud.gateway.gateway;

import com.google.common.base.Strings;
import com.netflix.zuul.context.RequestContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.cloud.client.loadbalancer.RetryableStatusCodeException;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeoutException;

/**
 * 通用错误处理器.
 *
 * @author: fenglibin
 * @date: 2017-10-16 15:25
 */
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
@Controller
@Component
@RequestMapping("${server.error.path:${error.path:/error}}")
public class ApiExceptionHandler extends AbstractErrorController {


    private static final Logger logger = LoggerFactory.getLogger(ApiExceptionHandler.class);

    private final static String ERROR_OCCURRED = "error occurred";
    private final static String NO_HANDLER_FOUNF = "No handler found for : ";
    private final static String EXECUTE_SQL_FAILED = "Executing SQL failed : ";
    private final static String ACCESS_DENIED = "Role not match : ";
    private final static String METHOD_NOT_SUPPORT = " method is not supported for this request. Supported methods are : ";
    private final static String MISSING_REQUEST_PARA = " parameter is missing ";
    private HttpHeaders headers = new HttpHeaders();
    @Value("${server.error.path:${error.path:/error}}")
    private final String errorPath = "/error";

    public ApiExceptionHandler(ErrorAttributes errorAttributes) {
        super(errorAttributes);
        headers.setContentType(MediaType.APPLICATION_JSON);
    }

    @RequestMapping
    @ResponseBody
    public ResponseEntity<?> handleErrors(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpStatus status = getStatus(request);
        if (status == HttpStatus.NOT_FOUND) {
            return notFound(request);
        }
        return serverError(request, response);
    }


    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResponseEntity<?> serverError(HttpServletRequest request, HttpServletResponse response) {

        HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;

        RequestContext ctx = RequestContext.getCurrentContext();

        String uri = ctx.get("requestURI") == null ? request.getRequestURI() : ctx.get("requestURI").toString();

        String serviceId = ctx.get("serviceId") == null ? "" : ctx.get("serviceId").toString();

        String message = (String) ctx.get("error.message");


        if (ctx.getThrowable() != null) {
            Throwable re = getOriginException(ctx.getThrowable());
            if (re instanceof RetryableStatusCodeException) {
                // super(String.format("Service %s returned a status code of %d", serviceId, statusCode));
                message = re.getMessage();
                //最后三位是状态码
                if (!Strings.isNullOrEmpty(message)) {
                    try {
                        int httpCode = Integer.parseInt(message.substring(message.length() - 3));
                        httpStatus = HttpStatus.valueOf(httpCode);
                    } catch (Exception ex){
                        logger.error("RetryableStatusCodeException",ex);
                    }
                    finally {
                    }
                }
                logger.warn("uri:{},error:{}", uri, re.getMessage());
            } else if(re instanceof TimeoutException){
                message = serviceId + " hystrix Timeout";
                httpStatus = HttpStatus.GATEWAY_TIMEOUT;
                logger.warn("uri:{},error:{}", uri, re.getMessage());
            }
            else if (re instanceof java.net.ConnectException) {
                message = serviceId + " Service Unavailable";
                httpStatus = HttpStatus.SERVICE_UNAVAILABLE;
                logger.warn("uri:{},error:{}", uri, re.getMessage());

            } else if (re instanceof java.net.SocketTimeoutException) {
                message = serviceId + " Connection Timeout";
                httpStatus = HttpStatus.GATEWAY_TIMEOUT;
                logger.warn("uri:{},error:{}", uri, re.getMessage());
            } else if (re instanceof com.netflix.client.ClientException) {
                message = re.getMessage();
                logger.warn("uri:{},error:{}", uri, re.getMessage());
            } else {
                logger.warn("Error during filtering", re);
            }
        }

        ApiDefaultErrorMessage errorMessage =
                new ApiDefaultErrorMessage(httpStatus, message, ERROR_OCCURRED);
        return new ResponseEntity<>(errorMessage, new HttpHeaders(), httpStatus);
    }


    public ResponseEntity<?> notFound(HttpServletRequest request) {
        String msg = "Api Service Shutdown";
        if (request.getAttribute("javax.servlet.error.request_uri") != null) {
            String requestUri = request.getAttribute("javax.servlet.error.request_uri").toString();

            if (!Strings.isNullOrEmpty(requestUri)) {
                String[] path = requestUri.split("/");
                if (path.length > 2) {
                    msg = path[2] + " Service Unavailable";
                }
            }
        }

        //todo:HttpStatus.NOT_FOUND
        ApiDefaultErrorMessage errorMessage = new ApiDefaultErrorMessage(HttpStatus.SERVICE_UNAVAILABLE, msg, "");
        return new ResponseEntity<>(errorMessage, headers, HttpStatus.OK);
    }


    @Override
    public String getErrorPath() {
        return errorPath;
    }

    private Throwable getOriginException(Throwable e) {
        e = e.getCause();
        while (e.getCause() != null) {
            e = e.getCause();
        }
        return e;
    }
}

